package com.gitee.wsl.platform.concurrent.locks

import kotlin.time.Duration
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.time.Duration.Companion.seconds


@Suppress("EmptyDefaultConstructor")
expect open class ConditionLock() {

    fun lock()

    fun unlock()

    /**
     * Causes the current thread to wait until it is signalled or interrupted,
     * or the specified waiting time elapses.
     *
     * @param timeout the maximum time to wait.
     * @return an estimate of the [timeout] value minus the time spent waiting upon return from this method.
     * A positive value may be used as the argument to a subsequent call to this method to finish waiting out
     * the desired time. A value less than or equal to zero indicates that no time remains.
     */
    fun await(timeout: Duration = Duration.INFINITE): Duration

    /** Wakes up all waiting threads. */
    fun signal()
}


@OptIn(ExperimentalContracts::class)
inline fun <T> ConditionLock.synchronized(block: () -> T): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }

    lock()
    try {
        return block()
    } finally {
        unlock()
    }
}


inline fun ConditionLock.waitFor(timeout: Duration, predicate: () -> Boolean): Boolean {
    require(!timeout.isNegative()) { "Timeout must not be negative" }

    var remainingTimeout = timeout

    while (true) {
        if (predicate()) {
            return true
        }
        if (!remainingTimeout.isPositive()) {
            return false
        }

        remainingTimeout = await(timeout = timeout)
    }
}


fun ConditionLock.waitForOrFail(timeout: Duration = 5.seconds, predicate: () -> Boolean) {
    if (!waitFor(timeout, predicate)) {
        error("Timeout waiting for condition")
    }
}
